﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;

//[ExecuteInEditMode]
[AddComponentMenu("PUEX/ParticleSystem")]
public class PUEXParticleSystem : MonoBehaviour {
	
	#region - Editable property
	public bool destroyOnFinish;
	public bool inheritOwnerTranslation;
	public bool inheritOwnerRotation;
	public bool inheritOwnerScale;
	#endregion

	#region - Property
	[HideInInspector]
	public bool autoStart;
	
	[HideInInspector]
	public float playSpeed;
	public float getPlaySpeed () { return playSpeed; }
	public void setPlaySpeed (float value) { playSpeed = value; }
	
	protected bool _prepared;
	protected bool _running;
	public bool isRuned () { return _running; }
	protected bool _paused;
	public bool isPaused () { return _paused; }

	protected bool _dymParamsDirty;

	[HideInInspector]
	public float timeElapsedSinceStart;
	public float getTimeElapsedSinceStart () { return timeElapsedSinceStart; }
	public void setTimeElapsedSinceStart (float value) { timeElapsedSinceStart = value; }
	
	public Vector3 position {
		get {
			return transform.localPosition;
		}
		set {
			transform.localPosition = value;
		}
	}
	public Quaternion rotation {
		get {
			return transform.localRotation;
		}
		set {
			transform.localRotation = value;
		}
	}
	public Vector3 scale {
		get {
			return transform.localScale;
		}
		set {
			transform.localScale = value;
		}
	}

	[HideInInspector]
	public Matrix4x4 systemToWorldTransform;
	[HideInInspector]
	public Vector3 systemToWorldScale3;
	[HideInInspector]
	public float systemToWorldScale;

	[HideInInspector]
	public List<PUEXEmitter> emitters;
	[HideInInspector]
	public Dictionary<string, float> dynParams;
	[HideInInspector]
	public Dictionary<string, PUEXParticleSystem> subParticleSystemTemplates;
	[HideInInspector]
	public PUEXParticleSystem ownerParticleSystem;

	[HideInInspector]
	public bool isSubParticleSystemTemplate;
	#endregion
	
	#region - Lifecycle
	public PUEXParticleSystem ()
	{
		destroyOnFinish = false;
		inheritOwnerTranslation = false;
		inheritOwnerRotation = false;
		inheritOwnerScale = false;

		autoStart = true;
		playSpeed = 1f;

		_prepared = false;
		_running = false;
		_paused = false;
		_dymParamsDirty = false;
		timeElapsedSinceStart = 0.0f;

		ownerParticleSystem = null;

		isSubParticleSystemTemplate = false;
	}

	public void Awake ()
	{
		if (transform.parent != null && transform.parent.GetComponent<PUEXParticleSystem> () != null) {
			// this particle system is a sub system template
			isSubParticleSystemTemplate = true;

			gameObject.SetActive(false);
		} else {
			emitters = new List<PUEXEmitter> ();
			for (int i = 0; i < transform.childCount; i++) {
				var emitter = transform.GetChild(i).GetComponent<PUEXEmitter>();
				if (emitter)
					emitters.Add(emitter);
			}
			
			dynParams = new Dictionary<string, float> ();

			subParticleSystemTemplates = new Dictionary<string, PUEXParticleSystem>();
			for (int i = 0; i < transform.childCount; i++) {
				var subSystemTemplate = transform.GetChild(i).GetComponent<PUEXParticleSystem>();
				if (subSystemTemplate)
					subParticleSystemTemplates.Add (subSystemTemplate.gameObject.name, subSystemTemplate);
			}
		}
	}

	public void Start ()
	{
		prepared ();

		if (autoStart)
			startParticleSystem ();
	}

	public void OnEnable ()
	{
		if (Application.isPlaying && _prepared) {
			startParticleSystem ();
		} else {
			autoStart = true;
		}
	}

	public void OnDisable ()
	{
		if (Application.isPlaying && _prepared) {
			stopParticleSystem ();
		} else {
			autoStart = false;
		}
	}

	public void FixedUpdate ()
	{
		_update (Time.fixedDeltaTime * playSpeed);
	}
	
	public void OnRenderObject ()
	{
		_visit ();
		_draw ();
	}
	#endregion

	#region - State
	protected void prepared ()
	{
		if (!_prepared) {
			foreach (var emitter in emitters) {
				if (emitter.gameObject.activeSelf)
					emitter.prepared (this);
			}
			
			timeElapsedSinceStart = 0.0f;
			
			_prepared = true;
		}
	}
	
	protected void start ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.start ();
		}
	}
	
	protected void pause ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.pause ();
		}
	}
	
	protected void resume ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.resume ();
		}
	}
	
	protected void stop ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.stop ();
		}
	}
	
	protected void unPrepared ()
	{
		if (_prepared) {
			foreach (var emitter in emitters) {
				if (emitter.gameObject.activeSelf)
					emitter.unPrepared ();
			}
			
			_prepared = false;
		}
	}
	#endregion

	#region - Update
	protected void _update (float dt)
	{
		if (_paused)
			return;

		if (!anyRunningEmitter() && getAliveParticleCount() <= 0) {
			forceStopParticleSystem ();
			return;
		}

		updateTransform ();

		if (_dymParamsDirty) {
			updateParams();
			_dymParamsDirty = false;
		}

		foreach (var emitter in emitters)
		{
			if (emitter.gameObject.activeSelf)
				emitter.update (dt);
		}

		timeElapsedSinceStart += dt;
	}

	protected void updateTransform () {
		systemToWorldTransform = transform.localToWorldMatrix;
		systemToWorldScale3 = transform.lossyScale;
		systemToWorldScale = systemToWorldScale3.x;
		systemToWorldScale += systemToWorldScale3.y;
		systemToWorldScale += systemToWorldScale3.z;
		systemToWorldScale /= 3f;
	}
	#endregion

	#region - Draw
	protected void _visit ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.visit ();
		}
	}

	protected void _draw ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.draw ();
		}
	}
	#endregion

	#region - Method
	public void startParticleSystem ()
	{
		if (_running == false) {
			prepared ();
			start ();

			_running = true;
		}
	}

	public void stopParticleSystem ()
	{
		if (_running == true) {
			stop ();

			_running = false;
		}
	}

	public void forceStopParticleSystem ()
	{
		stopParticleSystem ();
		unPrepared ();

		if (destroyOnFinish) {
			Destroy(gameObject);
		}
	}

	public void pauseParticleSystem ()
	{
		if (!_paused) {
			pause ();

			_paused = true;
		}
	}

	public void resumeParticleSystem ()
	{
		if (_paused) {
			resume ();

			_paused = false;
		}
	}

	public void forceEmission(string emitterName, int requested)
	{
		if (_running) {
			PUEXEmitter e = null;
			foreach (var emitter in emitters) {
				if (emitter.emitterName == emitterName) {
					e = emitter;
					break;
				}
			}

			if (e) {
				e.emitParticles (requested); 
			}
		}
	}

	public void forceEmission(int requested)
	{
		if (_running) {
			foreach (var emitter in emitters) {
				emitter.emitParticles(requested);
			}
		}
	}

	public void setEmitterVisible (string emitterName, bool visible) {
		foreach (var emitter in emitters) {
			if (emitter.emitterName == emitterName) {
				emitter.setVisible(visible);
				return;
			}
		}
	}

	public bool isEmitterVisible (string emitterName) {
		foreach (var emitter in emitters) {
			if (emitter.emitterName == emitterName) {
				return emitter.isVisible();
			}
		}
		return false;
	}

	public bool anyRunningEmitter ()
	{
		foreach (var emitter in emitters) {
			if (emitter.isRunning())
				return true;
		}
		return false;
	}

	public int getAliveParticleCount ()
	{
		int count = 0;

		foreach (var emitter in emitters)
			count += emitter.aliveParticleCount;

		return count;
	}
	#endregion

	#region - Event
	public void notifyEvent (string eventName) {
		foreach (var emitter in emitters) {
			emitter.notifyEvent(eventName);
		}
	}

	public void dispatchEvent (string eventName) {
		Debug.Log ("DISPATCH EVENT:" + eventName);

		//TBD
	}
	#endregion

	#region - Param property
	public void setParamFloat (string name, float value)
	{
		dynParams [name] = value;

		_dymParamsDirty = true;
	}
	public bool getParamFloat (string name, out float value)
	{
		if (dynParams.ContainsKey (name)) {
			value = dynParams[name];
			return true;
		}
		value = 0f;
		return false;
	}

	public void setParamVec2 (string name, Vector2 value)
	{
		dynParams [name + ".x"] = value.x;
		dynParams [name + ".y"] = value.y;

		_dymParamsDirty = true;
	}
	public bool getParamVec2 (string name, out Vector2 value)
	{
		string xn = name + ".x";
		string yn = name + ".y";
		if (dynParams.ContainsKey (xn) &&
		    dynParams.ContainsKey (yn)) {
			value = new Vector2(dynParams [xn], dynParams [yn]);
			return true;
		}
		value = Vector2.zero;
		return false;
	}

	public void setParamVec3 (string name, Vector3 value)
	{
		dynParams [name + ".x"] = value.x;
		dynParams [name + ".y"] = value.y;
		dynParams [name + ".z"] = value.z;
		
		_dymParamsDirty = true;
	}
	public bool getParamVec3 (string name, out Vector3 value)
	{
		string xn = name + ".x";
		string yn = name + ".y";
		string zn = name + ".z";
		if (dynParams.ContainsKey (xn) &&
		    dynParams.ContainsKey (yn) &&
		    dynParams.ContainsKey (zn)) {
			value = new Vector3(dynParams [xn], dynParams [yn], dynParams [zn]);
			return true;
		}
		value = Vector3.zero;
		return false;
	}

	public void setParamVec4 (string name, Vector4 value)
	{
		dynParams [name + ".x"] = value.x;
		dynParams [name + ".y"] = value.y;
		dynParams [name + ".z"] = value.z;
		dynParams [name + ".w"] = value.w;
		
		_dymParamsDirty = true;
	}
	public bool getParamVec4 (string name, out Vector4 value)
	{
		string xn = name + ".x";
		string yn = name + ".y";
		string zn = name + ".z";
		string wn = name + ".w";
		if (dynParams.ContainsKey (xn) &&
		    dynParams.ContainsKey (yn) &&
		    dynParams.ContainsKey (zn) &&
			dynParams.ContainsKey (wn)) {
			value = new Vector4(dynParams[xn], dynParams[yn], dynParams[zn], dynParams[wn]);
			return true;
		}
		value = Vector4.zero;
		return false;
	}

	public void setParamColor (string name, Color value)
	{
		dynParams [name + ".r"] = value.r;
		dynParams [name + ".g"] = value.g;
		dynParams [name + ".b"] = value.b;
		dynParams [name + ".a"] = value.a;
		
		_dymParamsDirty = true;
	}
	public bool getParamColor (string name, out Color value)
	{
		string rn = name + ".r";
		string gn = name + ".g";
		string bn = name + ".b";
		string an = name + ".a";
		if (dynParams.ContainsKey (rn) &&
		    dynParams.ContainsKey (gn) &&
		    dynParams.ContainsKey (bn) &&
		    dynParams.ContainsKey (an)) {
			value = new Color(dynParams[rn], dynParams[gn], dynParams[bn], dynParams[an]);
			return true;
		}
		value = Color.black;
		return false;
	}

	protected void updateParams ()
	{
		foreach (var emitter in emitters) {
			if (emitter.gameObject.activeSelf)
				emitter.updateParams(this);
		}
	}
	#endregion

	#region - Serialization
	public virtual void save (JsonData rootData)
	{
		JsonData ret = new JsonData ();
		
		rootData [gameObject.name] = ret;
		ret ["destroy_on_finish"] = destroyOnFinish;
		ret ["inherit_owner_translation"] = inheritOwnerTranslation;
		ret ["inherit_owner_rotation"] = inheritOwnerRotation;
		ret ["inherit_owner_scale"] = inheritOwnerScale;
		ret ["auto_start"] = enabled;

		JsonData emitterArray = new JsonData ();
		emitterArray.SetJsonType (JsonType.Array);
		for (int i = 0; i < transform.childCount; i++) {
			var emitter = transform.GetChild(i).GetComponent<PUEXEmitter>();
			if (emitter) {
				JsonData emitterData = emitter.save (ret);

				emitterArray.Add(emitterData);
			}
		}
		ret ["emitters"] = emitterArray;

		// sub particle system templates
		for (int i = 0; i < transform.childCount; i++) {
			var subSystemTemplate = transform.GetChild(i).GetComponent<PUEXParticleSystem>();
			if (subSystemTemplate) {
				subSystemTemplate.save (rootData);
			}
		}
	}
	#endregion

}
